# 导入必要的库
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # 用于3D可视化
import seaborn as sns  # 用于绘制混淆矩阵热力图
from sklearn.datasets import load_digits  # 加载手写数字数据集
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score  # 数据划分和模型评估工具
from sklearn.svm import SVC  # 支持向量机分类器
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix  # 模型评估指标
from sklearn.preprocessing import StandardScaler  # 数据标准化
from sklearn.decomposition import PCA  # 主成分分析，用于降维

# 设置matplotlib的中文字体（解决中文显示问题）
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

# 1. 加载数据集
# 使用手写数字数据集（digits），包含1797个样本，每个样本有64个特征（8x8像素图像）
digits = load_digits()
X = digits.data  # 特征矩阵：1797 x 64
y = digits.target  # 目标变量：0-9的数字标签

# 2. 数据预处理
# SVM对特征尺度敏感，因此需要标准化数据（均值为0，方差为1）
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# 3. 划分训练集和测试集
# 将数据集划分为80%训练集和20%测试集，设置随机种子以确保结果可复现
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2, random_state=42)

# 4. 超参数调优
# 使用GridSearchCV进行超参数搜索，调优C（惩罚参数）和gamma（RBF核参数）
param_grid = {
    'C': [0.1, 1, 10, 100],  # 惩罚参数C的不同取值
    'gamma': [0.001, 0.01, 0.1, 1],  # RBF核函数的gamma参数
    'kernel': ['rbf']  # 使用径向基函数核（RBF），适合非线性问题
}
svm = SVC()  # 初始化SVM分类器
# GridSearchCV结合5折交叉验证，搜索最佳参数组合，n_jobs=-1表示使用所有CPU核心加速
grid_search = GridSearchCV(svm, param_grid, cv=5, scoring='accuracy', n_jobs=-1)
grid_search.fit(X_train, y_train)  # 训练并搜索

# 输出最佳参数和交叉验证得分
print("最佳参数:", grid_search.best_params_)
print("最佳交叉验证得分:", grid_search.best_score_)

# 5. 训练最终模型
# 使用最佳参数训练最终的SVM模型
best_model = grid_search.best_estimator_
best_model.fit(X_train, y_train)

# 6. 模型预测
# 使用训练好的模型对测试集进行预测
y_pred = best_model.predict(X_test)

# 7. 模型评估
# 计算测试集的准确率并打印详细的分类报告
accuracy = accuracy_score(y_test, y_pred)
print("\n测试集准确率:", accuracy)
print("\n分类报告:\n", classification_report(y_test, y_pred))

# 8. 混淆矩阵
# 计算混淆矩阵并用热力图可视化
cm = confusion_matrix(y_test, y_pred)
plt.figure(figsize=(10, 8))
# 使用seaborn的heatmap绘制混淆矩阵，annot=True显示数字，fmt='d'表示整数格式
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=np.unique(y), yticklabels=np.unique(y))
plt.title('混淆矩阵')  # 设置标题为中文
plt.xlabel('预测标签')  # X轴标签
plt.ylabel('真实标签')  # Y轴标签
plt.show()

# 9. 交叉验证评分
# 使用5折交叉验证评估模型在整个数据集上的稳定性
cv_scores = cross_val_score(best_model, X_scaled, y, cv=5)
print("\n5折交叉验证得分:", cv_scores)
print("平均交叉验证得分:", cv_scores.mean())
print("交叉验证得分标准差:", cv_scores.std())

# 10. 降维并可视化（使用PCA）
# 将64维数据降维到2D以绘制决策边界
pca_2d = PCA(n_components=2)  # 设置PCA降维到2维
X_pca_2d = pca_2d.fit_transform(X_scaled)  # 应用PCA转换

# 训练降维后的SVM模型（仅用于可视化）
svm_2d = SVC(kernel='rbf', C=grid_search.best_params_['C'], gamma=grid_search.best_params_['gamma'])
# 划分降维后的训练和测试集
X_train_2d, X_test_2d, y_train_2d, y_test_2d = train_test_split(X_pca_2d, y, test_size=0.2, random_state=42)
svm_2d.fit(X_train_2d, y_train_2d)

# 创建网格以绘制决策边界
h = 0.02  # 网格步长
x_min, x_max = X_pca_2d[:, 0].min() - 1, X_pca_2d[:, 0].max() + 1  # X轴范围
y_min, y_max = X_pca_2d[:, 1].min() - 1, X_pca_2d[:, 1].max() + 1  # Y轴范围
xx, yy = np.meshgrid(np.arange(x_min, x_max, h), np.arange(y_min, y_max, h))  # 创建网格点
Z = svm_2d.predict(np.c_[xx.ravel(), yy.ravel()])  # 预测网格点的分类
Z = Z.reshape(xx.shape)  # 重塑为网格形状

# 绘制2D决策边界
plt.figure(figsize=(10, 8))
plt.contourf(xx, yy, Z, cmap=plt.cm.RdYlBu, alpha=0.4)  # 绘制分类区域
plt.scatter(X_pca_2d[:, 0], X_pca_2d[:, 1], c=y, cmap=plt.cm.RdYlBu, edgecolor='k')  # 绘制数据点
plt.xlabel('第一主成分')  # X轴标签
plt.ylabel('第二主成分')  # Y轴标签
plt.title('SVM决策边界（2D PCA投影）')  # 设置标题
plt.colorbar(label='数字类别')  # 添加颜色条
plt.show()

# 11. 3D可视化
# 将64维数据降维到3D以进一步可视化
pca_3d = PCA(n_components=3)  # 设置PCA降维到3维
X_pca_3d = pca_3d.fit_transform(X_scaled)  # 应用PCA转换

fig = plt.figure(figsize=(12, 10))
ax = fig.add_subplot(111, projection='3d')  # 创建3D坐标轴
scatter = ax.scatter(X_pca_3d[:, 0], X_pca_3d[:, 1], X_pca_3d[:, 2], c=y, cmap=plt.cm.RdYlBu)  # 绘制3D散点图
ax.set_xlabel('第一主成分')  # X轴标签
ax.set_ylabel('第二主成分')  # Y轴标签
ax.set_zlabel('第三主成分')  # Z轴标签
plt.title('SVM分类（3D PCA投影）')  # 设置标题
plt.colorbar(scatter, label='数字类别')  # 添加颜色条
plt.show()

# 12. 学习曲线
# 绘制训练和测试准确率随训练数据量变化的曲线，评估模型的过拟合或欠拟合
train_sizes, train_scores, test_scores = [], [], []
# 逐步增加训练数据量（从10%到100%）
for size in np.linspace(0.1, 1.0, 10):
    n_size = int(size * len(X_train))  # 当前训练数据量
    X_train_subset = X_train[:n_size]  # 取训练数据子集
    y_train_subset = y_train[:n_size]  # 取对应标签
    # 训练模型并预测
    svm_fit = SVC(**grid_search.best_params_).fit(X_train_subset, y_train_subset)
    train_pred = svm_fit.predict(X_train_subset)
    test_pred = svm_fit.predict(X_test)
    # 记录训练和测试准确率
    train_sizes.append(n_size)
    train_scores.append(accuracy_score(y_train_subset, train_pred))
    test_scores.append(accuracy_score(y_test, test_pred))

# 绘制学习曲线
plt.figure(figsize=(10, 6))
plt.plot(train_sizes, train_scores, label='训练准确率')  # 训练准确率曲线
plt.plot(train_sizes, test_scores, label='测试准确率')  # 测试准确率曲线
plt.xlabel('训练样本数量')  # X轴标签
plt.ylabel('准确率')  # Y轴标签
plt.title('学习曲线')  # 设置标题
plt.legend()  # 显示图例
plt.grid(True)  # 显示网格
plt.show()